﻿/* Copyright (C) Siemens AG, 2012. All Rights Reserved.
 * Author: Roland Eckl, Corporate Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections;
using System.ServiceModel;


namespace RVM.Infrastructure.Communication.WCF.NetPipe
{
    //TODO: Add importing and/or exporting ID, so exporting entity can see all importers!

    /// <summary>
    /// Agent allows to access registered viewmodels in different processes/appdomains.
    /// </summary>
    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.PerSession)]
    public class NetPipeViewModelAgent : IViewModelAgent
    {
        #region Private Members

        /// <summary>
        /// Registry for local viewmodels which are exported.
        /// </summary>
        private ViewModelRegistry _registry = ViewModelRegistry.Shared;

        /// <summary>
        /// Associated exporter.
        /// </summary>
        private NetPipeViewModelExporter _exporter = null;

        /// <summary>
        /// Callback Channel.
        /// </summary>
        private IViewModelCallback _callbackChannel = null;

        /// <summary>
        /// Callback's Service Channel.
        /// </summary>
        private IServiceChannel _callbackServiceChannel = null;

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor, simply retrieves the callback channel from the operation context
        /// and uses the shared ViewModelRegistry.
        /// </summary>
        public NetPipeViewModelAgent() : this(ViewModelRegistry.Shared, null) { }

        /// <summary>
        /// Constructor receives the callback channel and sets a dedicated registry.
        /// </summary>
        /// <param name="registry">Registry which is used to access viewmodels</param>
        /// <param name="exporter">Associated exporter</param>
        public NetPipeViewModelAgent(ViewModelRegistry registry, IViewModelExporter exporter)
        {
            _callbackChannel = OperationContext.Current.GetCallbackChannel<IViewModelCallback>();
            _callbackServiceChannel = ((IServiceChannel)_callbackChannel);
            _callbackServiceChannel.Closing += new EventHandler(OnCallbackChannelClosing);
            _registry = registry;
            _exporter = exporter as NetPipeViewModelExporter;
            _exporter.AddImporter(this);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Checks if a ViewModel is already registered for export.
        /// </summary>
        /// <param name="id">Unique ID of ViewModel</param>
        /// <returns>TRUE, if ViewModel is already registered</returns>
        public bool Has(string id)
        {
            return _registry.Contains(id);
        }

        /// <summary>
        /// Registers an agent at some exported remote ViewModel.
        /// </summary>
        /// <param name="id">Unique ID</param>
        /// <returns>TRUE, if ViewModel was not registered before</returns>
        public bool Register(string id)
        {
            if (!Has(id)) return false;
            RemotableViewModel rvm = _registry.Get(id);
            rvm.AddImportingViewModelAgent(this);
            return true;
        }

        /// <summary>
        /// Removes an agent from some exported remote ViewModel.
        /// </summary>
        /// <param name="id">Unique ID</param>
        /// <returns>TRUE, if ViewModel was registered before and could be removed</returns>
        public bool Unregister(string id)
        {
            if (!Has(id)) return false;
            RemotableViewModel rvm = _registry.Get(id);
            rvm.RemoveViewModelAgent(this);
            return true;
        }

        /// <summary>
        /// Fires the property changed event on the callback channel.
        /// </summary>
        /// <param name="id">Unique ID</param>
        /// <param name="property">Affected property</param>
        public void FirePropertyChanged(string id, string property)
        {
            _callbackChannel.FireRemotePropertyChanged(id, property);
        }

        /// <summary>
        /// Fires the collection changed event on the callback channel.
        /// </summary>
        /// <param name="id">Unique ID</param>
        /// <param name="property">Affected property</param>
        /// <param name="details">Details on performed action</param>
        public void FireCollectionChanged(string id, string property, CollectionChangedEventArgs details)
        {
            _callbackChannel.FireRemoteCollectionChanged(id, property, details);
        }

        /// <summary>
        /// Sets the value of a property.
        /// </summary>
        /// <param name="id">Unique ID of ViewModel</param>
        /// <param name="property">Affected property</param>
        /// <param name="value">Property's value</param>
        /// <param name="supressNotification">Should PropertyChanged event be supressed?</param>
        public void SetValue(string id, string property, object value, bool supressNotification)
        {
            if(Has(id))
                _registry.Get(id).SetValue(property, value, supressNotification);
        }

        /// <summary>
        /// Gets the value of a property
        /// </summary>
        /// <param name="id">Unique ID of ViewModel</param>
        /// <param name="property">Affected property</param>
        /// <param name="defaultvalue">Value if property is not available</param>
        /// <returns>Value of property of default value</returns>
        public object GetValue(string id, string property, object defaultvalue)
        {
            object res = null;
            if (Has(id))
                res = _registry.Get(id).GetValue(property, defaultvalue);
            return res;
        }

        /// <summary>
        /// Gets a collection instead of a single collection. (Due to serialization problems for array as object)
        /// </summary>
        /// <param name="id">Unique ID of ViewModel</param>
        /// <param name="property">Affected property</param>
        /// <returns>collection of property or NULL</returns>
        public IList GetCollection(string id, string property)
        {
            if (Has(id))
                return _registry.Get(id).GetCollection(property);
            return null;
        }

        /// <summary>
        /// Executes a command.
        /// </summary>
        /// <param name="id">Unique ID of ViewModel</param>
        /// <param name="command">Affected command</param>
        /// <param name="parameter">Parameters to hand over</param>
        public void ExecuteCommand(string id, string command, object parameter)
        {
            if(Has(id))
                _registry.Get(id).ExecuteCommand(command, parameter);
        }

        /// <summary>
        /// Checks a predicate for a command.
        /// </summary>
        /// <param name="id">Unique ID of ViewModel</param>
        /// <param name="command">Affected command</param>
        /// <param name="parameter">Paramters to hand over</param>
        /// <returns></returns>
        public bool CanExecuteCommand(string id, string command, object parameter)
        {
            if (Has(id))
                return _registry.Get(id).CanExecuteCommand(command, parameter);
            return true;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Event Handler raised whenever the callback channel gets closed (planned or caused through some error).
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnCallbackChannelClosing(object sender, EventArgs e)
        {
            _exporter.RemoveImporter(this);
            _registry.RemoveAgentFromAll(this);
        }

        #endregion
    }
}
